﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Library.Linq;
using INDEX = System.UInt64;

namespace Library.Collections.Specialized
{
    public class WrappedEnumerable<T> : IEnumerable<T>,
        IAggregate<T>,
        IAverage<T>,
        ICast,
        IConcat<T>,
        IContains<T>,
        ICountable,
        IDistinct<T>,
        IElementAt<T>,
        IExcept<T>,
        IFirst<T>,
        IGroupBy<T>,
        IGroupJoin<T>,
        IIntersect<T>,
        IJoin<T>,
        ILast<T>,
        IMaximum<T>,
        IMinimum<T>,
        IOfType,
        IQuantifiable<T>,
        IReverse<T>,
        ISelect<T>,
        ISelectMany<T>,
        ISingle<T>,
        ISkip<T>,
        ISkipWhile<T>,
        ISum<T>,
        ITake<T>,
        ITakeWhile<T>,
        IUnion<T>,
        IWhere<T>
    {
        public WrappedEnumerable(IEnumerable<T> enumerable)
        {
            this.wrapped = enumerable;
        }

        internal IEnumerable<T> wrapped;
        protected IEnumerable<T> Wrapped
        {
            get
            {
                return wrapped;
            }
        }

        public virtual IEnumerator<T> GetEnumerator()
        {
            return wrapped.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public virtual T Aggregate(Func<T, T, T> func)
        {
            return Library.Linq.Enumerable.Aggregate(wrapped, func);
        }

        public virtual TAccumulate Aggregate<TAccumulate>(TAccumulate seed, Func<TAccumulate, T, TAccumulate> func)
        {
            return Library.Linq.Enumerable.Aggregate(wrapped, seed, func);
        }

        public virtual TResult Aggregate<TAccumulate, TResult>(TAccumulate seed, Func<TAccumulate, T, TAccumulate> func, Func<TAccumulate, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.Aggregate(wrapped, seed, func, resultSelector);
        }

        public virtual bool Average(out T item)
        {
            IAverage<T> average = wrapped as IAverage<T>;
            if (average != null)
                return average.Average(out item);

            MethodInfo method = typeof(Library.Linq.Enumerable).GetMethod("Average", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new Type[] { typeof(IEnumerable<T>) }, null);
            if (method != null)
            {
                item = (T)method.Invoke(null, new object[] { wrapped });
                return true;
            }

            item = default(T);
            return false;
        }

        public virtual IEnumerable<TResult> Cast<TResult>()
        {
            return Library.Linq.Enumerable.Cast<TResult>(wrapped);
        }

        public virtual IEnumerable<T> Concat(T item)
        {
            return Library.Linq.Enumerable.Concat(wrapped, item);
        }

        public virtual IEnumerable<T> Concat(IEnumerable<T> enumerable)
        {
            return Library.Linq.Enumerable.Concat(wrapped, enumerable);
        }

        public virtual bool Contains(T item)
        {
            return Library.Linq.Enumerable.Contains(wrapped, item);
        }

        public virtual bool Contains(T item, IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Contains(wrapped, item, comparer);
        }

        public virtual INDEX Count
        {
            get
            {
                return Library.Linq.Enumerable.Count(wrapped);
            }
        }

        public virtual bool IsDistinct(out bool value)
        {
            IDistinct<T> distinct = wrapped as IDistinct<T>;
            if (distinct != null)
                return distinct.IsDistinct(out value);
            value = false;
            return false;
        }

        public virtual IEnumerable<T> Distinct()
        {
            return Library.Linq.Enumerable.Distinct(wrapped);
        }

        public virtual IEnumerable<T> Distinct(IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Distinct(wrapped, comparer);
        }

        public virtual bool ElementAt(INDEX index, out T item)
        {
            item = Library.Linq.Enumerable.ElementAt(wrapped, index);
            return true;
        }

        public virtual IEnumerable<T> Except(T item)
        {
            return Library.Linq.Enumerable.Except(wrapped, item);
        }

        public virtual IEnumerable<T> Except(T item, IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Except(wrapped, item, comparer);
        }

        public virtual IEnumerable<T> Except(IEnumerable<T> enumerable)
        {
            return Library.Linq.Enumerable.Except(wrapped, enumerable);
        }

        public virtual IEnumerable<T> Except(IEnumerable<T> enumerable, IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Except(wrapped, enumerable, comparer);
        }

        public virtual bool First(out T item)
        {
            try
            {
                item = Library.Linq.Enumerable.First(wrapped);
                return true;
            }
            catch
            {
                item = default(T);
                return false;
            }
        }

        public virtual IEnumerable<IGrouping<TKey, T>> GroupBy<TKey>(Func<T, TKey> keySelector)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector);
        }

        public virtual IEnumerable<IGrouping<TKey, T>> GroupBy<TKey>(Func<T, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, comparer);
        }

        public virtual IEnumerable<IGrouping<TKey, TElement>> GroupBy<TKey, TElement>(Func<T, TKey> keySelector, Func<T, TElement> elementSelector)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, elementSelector);
        }

        public virtual IEnumerable<IGrouping<TKey, TElement>> GroupBy<TKey, TElement>(Func<T, TKey> keySelector, Func<T, TElement> elementSelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, elementSelector, comparer);
        }

        public virtual IEnumerable<TResult> GroupBy<TKey, TResult>(Func<T, TKey> keySelector, Func<TKey, IEnumerable<T>, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, resultSelector);
        }

        public virtual IEnumerable<TResult> GroupBy<TKey, TResult>(Func<T, TKey> keySelector, Func<TKey, IEnumerable<T>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, resultSelector, comparer);
        }

        public virtual IEnumerable<TResult> GroupBy<TKey, TElement, TResult>(Func<T, TKey> keySelector, Func<T, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, elementSelector, resultSelector);
        }

        public virtual IEnumerable<TResult> GroupBy<TKey, TElement, TResult>(Func<T, TKey> keySelector, Func<T, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.GroupBy(wrapped, keySelector, elementSelector, resultSelector, comparer);
        }

        public virtual IEnumerable<TResult> GroupJoin<TInner, TKey, TResult>(IEnumerable<TInner> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<T, IEnumerable<TInner>, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.GroupJoin(wrapped, inner, outerKeySelector, innerKeySelector, resultSelector);
        }

        public virtual IEnumerable<TResult> GroupJoin<TInner, TKey, TResult>(IEnumerable<TInner> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<T, IEnumerable<TInner>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.GroupJoin(wrapped, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);
        }

        public virtual IEnumerable<T> Intersect(IEnumerable<T> enumerable)
        {
            return Library.Linq.Enumerable.Intersect(wrapped, enumerable);
        }

        public virtual IEnumerable<T> Intersect(IEnumerable<T> enumerable, IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Intersect(wrapped, enumerable, comparer);
        }

        public virtual IEnumerable<TResult> Join<TInner, TKey, TResult>(IEnumerable<TInner> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<T, TInner, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.Join(wrapped, inner, outerKeySelector, innerKeySelector, resultSelector);
        }

        public virtual IEnumerable<TResult> Join<TInner, TKey, TResult>(IEnumerable<TInner> inner, Func<T, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<T, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer)
        {
            return Library.Linq.Enumerable.Join(wrapped, inner, outerKeySelector, innerKeySelector, resultSelector, comparer);
        }

        public virtual bool Last(out T item)
        {
            try
            {
                item = Library.Linq.Enumerable.Last(wrapped);
                return true;
            }
            catch
            {
                item = default(T);
                return false;
            }
        }

        public virtual bool Maximum(out T item)
        {
            IMaximum<T> max = wrapped as IMaximum<T>;
            if (max != null)
                return max.Maximum(out item);

            MethodInfo method = typeof(Library.Linq.Enumerable).GetMethod("Maximum", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new Type[] { typeof(IEnumerable<T>) }, null);
            if (method != null)
            {
                item = (T)method.Invoke(null, new object[] { wrapped });
                return true;
            }

            item = default(T);
            return false;
        }

        public virtual bool Minimum(out T item)
        {
            IMinimum<T> min = wrapped as IMinimum<T>;
            if (min != null)
                return min.Minimum(out item);

            MethodInfo method = typeof(Library.Linq.Enumerable).GetMethod("Minimum", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new Type[] { typeof(IEnumerable<T>) }, null);
            if (method != null)
            {
                item = (T)method.Invoke(null, new object[] { wrapped });
                return true;
            }

            item = default(T);
            return false;
        }

        public virtual IEnumerable<TResult> OfType<TResult>()
        {
            return Library.Linq.Enumerable.OfType<TResult>(wrapped);
        }

        public virtual bool ForAll(Func<T, bool> predicate)
        {
            return Library.Linq.Enumerable.All(wrapped, predicate);
        }

        public virtual bool Exists()
        {
            return Library.Linq.Enumerable.Any(wrapped);
        }

        public virtual bool Exists(Func<T, bool> predicate)
        {
            return Library.Linq.Enumerable.Any(wrapped, predicate);
        }
        
        public virtual IEnumerable<T> Reverse()
        {
            return Library.Linq.Enumerable.Reverse(wrapped);
        }

        public virtual IEnumerable<TResult> Select<TResult>(Func<T, TResult> selector)
        {
            return Library.Linq.Enumerable.Select(wrapped, selector);
        }

        public virtual IEnumerable<TResult> Select<TResult>(Func<T, INDEX, TResult> selector)
        {
            return Library.Linq.Enumerable.Select(wrapped, selector);
        }

        public virtual IEnumerable<TResult> SelectMany<TResult>(Func<T, IEnumerable<TResult>> selector)
        {
            return Library.Linq.Enumerable.SelectMany(wrapped, selector);
        }

        public virtual IEnumerable<TResult> SelectMany<TResult>(Func<T, INDEX, IEnumerable<TResult>> selector)
        {
            return Library.Linq.Enumerable.SelectMany(wrapped, selector);
        }

        public virtual IEnumerable<TResult> SelectMany<TCollection, TResult>(Func<T, IEnumerable<TCollection>> collectionSelector, Func<T, TCollection, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.SelectMany(wrapped, collectionSelector, resultSelector);
        }

        public virtual IEnumerable<TResult> SelectMany<TCollection, TResult>(Func<T, INDEX, IEnumerable<TCollection>> collectionSelector, Func<T, TCollection, TResult> resultSelector)
        {
            return Library.Linq.Enumerable.SelectMany(wrapped, collectionSelector, resultSelector);
        }

        public virtual bool Single(out T item)
        {
            try
            {
                item = Library.Linq.Enumerable.Single(wrapped);
                return true;
            }
            catch
            {
                item = default(T);
                return false;
            }
        }

        public virtual IEnumerable<T> Skip(INDEX count)
        {
            return Library.Linq.Enumerable.Skip(wrapped, count);
        }

        public virtual IEnumerable<T> SkipWhile(Func<T, bool> predicate)
        {
            return Library.Linq.Enumerable.SkipWhile(wrapped, predicate);
        }

        public virtual IEnumerable<T> SkipWhile(Func<T, INDEX, bool> predicate)
        {
            return Library.Linq.Enumerable.SkipWhile(wrapped, predicate);
        }

        public virtual bool Sum(out T value)
        {
            ISum<T> sum = wrapped as ISum<T>;
            if (sum != null)
                return sum.Sum(out value);

            MethodInfo method = typeof(Library.Linq.Enumerable).GetMethod("Sum", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new Type[] { typeof(IEnumerable<T>) }, null);
            if (method != null)
            {
                value = (T)method.Invoke(null, new object[] { wrapped });
                return true;
            }

            value = default(T);
            return false;
        }

        public virtual IEnumerable<T> Take(INDEX count)
        {
            return Library.Linq.Enumerable.Take(wrapped, count);
        }

        public virtual IEnumerable<T> TakeWhile(Func<T, bool> predicate)
        {
            return Library.Linq.Enumerable.TakeWhile(wrapped, predicate);
        }

        public virtual IEnumerable<T> TakeWhile(Func<T, INDEX, bool> predicate)
        {
            return Library.Linq.Enumerable.TakeWhile(wrapped, predicate);
        }

        public virtual IEnumerable<T> Union(IEnumerable<T> enumerable)
        {
            return Library.Linq.Enumerable.Union(wrapped, enumerable);
        }

        public virtual IEnumerable<T> Union(IEnumerable<T> enumerable, IEqualityComparer<T> comparer)
        {
            return Library.Linq.Enumerable.Union(wrapped, enumerable, comparer);
        }

        public virtual IEnumerable<T> Where(Func<T, bool> functor)
        {
            return Library.Linq.Enumerable.Where(wrapped, functor);
        }

        public virtual IEnumerable<T> Where(Func<T, INDEX, bool> functor)
        {
            return Library.Linq.Enumerable.Where(wrapped, functor);
        }
    }
}
